package com.webchat.netty.server.config;


import com.webchat.netty.server.config.properties.NettyServerProperties;
import com.webchat.netty.server.handler.NettyWebSocketChannelInitializer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.context.event.EventListener;

@Slf4j
public class NettyWebSocketServer {

    /**
     * 服务端要使用两个线程组：
     *
     * boosGroup用于监听客户端连接，专门负责与客户端创建连接，并把连接注册到workerGroup的Selector中。
     * workerGroup用于处理每一个连接发生的读写事件。
     */
    private final EventLoopGroup bossGroup = new NioEventLoopGroup(1);

    private final EventLoopGroup workerGroup = new NioEventLoopGroup(10);

    /**
     * 服务端链接管道
     */
    private Channel serverChannel;

    @EventListener(ContextRefreshedEvent.class)
    public void start() throws InterruptedException {

        int port = NettyServerProperties.PORT;

        // 创建服务端启动器的工厂类
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(bossGroup, workerGroup)
                 // 设置通道类型，当建立连接后，会根据这个设置创建对应的channel实例
                 // NioSocketChannel：异步非阻塞的客户端TCP Socket连接。
                 // NioServerSocketChannel：异步非阻塞的服务端TCP Socket连接。
                 // 常用的就是这两个通道类型，因为是异步非阻塞的，所以是首选。
                 .channel(NioServerSocketChannel.class)
                 .handler(new LoggingHandler(LogLevel.INFO))
                 // 业务自定义handler链
                 .childHandler(new NettyWebSocketChannelInitializer())
                 // 设置的是服务端用于接收进来的数据，也就是boosGroup线程。
                 // 服务端接受连接的队列长度，如果队列已满，客户端连接将被拒绝。默认值，Windows为200，其它为128.
                 .option(ChannelOption.SO_BACKLOG, 1024)
                 // 提供给父管道接收到的连接，也就是workerGroup线程
                 // 连接保活，默认值为false。启用该功能时，TCP会主动探测空闲连接的有效性。
                 .childOption(ChannelOption.SO_KEEPALIVE, true);

        // 服务端或者客户端绑定服务器地址和端口号，默认是异步启动。加上sync()方法则是同步。
        ChannelFuture future = bootstrap.bind(port).sync();
        serverChannel = future.channel();
        log.info("Netty WebSocket Server started on port {}", port);
    }

    @PreDestroy
    @EventListener(ContextClosedEvent.class)
    public void stop() {
        if (serverChannel == null) {
            return;
        }
        serverChannel.close();
        if (!bossGroup.isShutdown()) {
            bossGroup.shutdownGracefully();
        }
        if (!workerGroup.isShutdown()) {
            workerGroup.shutdownGracefully();
        }
    }
}
